﻿using LightCAD.Core.Elements;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace LightCAD.Core
{
    public class Intersection
    {
        public string status { get; set; }
        public List<Vector2d> points { get; } = new List<Vector2d>();
        public Intersection() { }
        public Intersection(string status)
        {
            this.status = status;
        }

        public void appendPoints(IEnumerable<Vector2d> newPoints)
        {
            points.AddRange(newPoints);
        }
    }
    public static partial class GeoUtils
    {
        public static Vector2d[] ClosePolygon(Vector2d[] points)
        {
            var copy = new List<Vector2d>(points);
            copy.Add(points[0]);
            return copy.ToArray();
        }

        /**
    *  intersectCircleLine
    *
    *  @param {module:kld-intersections.Point2D} c
    *  @param {number} r
    *  @param {module:kld-intersections.Point2D} a1
    *  @param {module:kld-intersections.Point2D} a2
    *  @returns {module:kld-intersections.Intersection}
    */
        public static Intersection IntersectCircleLine(Vector2d c, double r, Vector2d a1, Vector2d a2)
        {
            var result = new Intersection();
            var a = (a2.X - a1.X) * (a2.X - a1.X) +
                     (a2.Y - a1.Y) * (a2.Y - a1.Y);
            var b = 2 * ((a2.X - a1.X) * (a1.X - c.X) +
                           (a2.Y - a1.Y) * (a1.Y - c.Y));
            var cc = c.X * c.X + c.Y * c.Y + a1.X * a1.X + a1.Y * a1.Y -
                     2 * (c.X * a1.X + c.Y * a1.Y) - r * r;
            var deter = b * b - 4 * a * cc;

            if (deter < 0)
            {
                result = new Intersection("Outside");
            }
            else if (deter == 0)
            {
                result = new Intersection("Tangent");
                // NOTE: should calculate this point
            }
            else
            {
                var e = Math.Sqrt(deter);
                var u1 = (-b + e) / (2 * a);
                var u2 = (-b - e) / (2 * a);

                if ((u1 < 0 || u1 > 1) && (u2 < 0 || u2 > 1))
                {
                    if ((u1 < 0 && u2 < 0) || (u1 > 1 && u2 > 1))
                    {
                        result = new Intersection("Outside");
                    }
                    else
                    {
                        result = new Intersection("Inside");
                    }
                }
                else
                {
                    result = new Intersection("Intersection");

                    if (0 <= u1 && u1 <= 1)
                    {
                        result.points.Add(a1.Lerp(a2, u1));
                    }

                    if (0 <= u2 && u2 <= 1)
                    {
                        result.points.Add(a1.Lerp(a2, u2));
                    }
                }
            }

            return result;
        }



        /**
    *  intersectCirclePolygon
    *
    *  @param {module:kld-intersections.Point2D} c
    *  @param {number} r
    *  @param {Array<module:kld-intersections.Point2D>} points
    *  @returns {module:kld-intersections.Intersection}
    */
        public static Intersection IntersectCirclePolygon(Vector2d c, double r, Vector2d[] points)
        {
            return IntersectCirclePolyline(c, r, ClosePolygon(points));
        }

        /**
         *  intersectCirclePolyline
         *
         *  @param {module:kld-intersections.Point2D} c
         *  @param {number} r
         *  @param {Array<module:kld-intersections.Point2D>} points
         *  @returns {module:kld-intersections.Intersection}
         */
        public static Intersection IntersectCirclePolyline(Vector2d c, double r, Vector2d[] points)
        {
            var result = new Intersection("None");
            Intersection inter = null;

            for (var i = 0; i < points.Length - 1; i++)
            {
                var a1 = points[i];
                var a2 = points[i + 1];

                inter = IntersectCircleLine(c, r, a1, a2);
                result.appendPoints(inter.points);
            }

            if (result.points.Count > 0)
            {
                result.status = "Intersection";
            }
            else if (inter != null)
            {
                result.status = inter.status;
            }

            return result;
        }

        /**
    *  intersectLineLine
    *
    *  @param {module:kld-intersections.Point2D} a1
    *  @param {module:kld-intersections.Point2D} a2
    *  @param {module:kld-intersections.Point2D} b1
    *  @param {module:kld-intersections.Point2D} b2
    *  @returns {module:kld-intersections.Intersection}
    */
        public static Intersection IntersectLineLine(Vector2d a1, Vector2d a2, Vector2d b1, Vector2d b2)
        {
            Intersection result;

            var ua_t = (b2.X - b1.X) * (a1.Y - b1.Y) - (b2.Y - b1.Y) * (a1.X - b1.X);
            var ub_t = (a2.X - a1.X) * (a1.Y - b1.Y) - (a2.Y - a1.Y) * (a1.X - b1.X);
            var u_b = (b2.Y - b1.Y) * (a2.X - a1.X) - (b2.X - b1.X) * (a2.Y - a1.Y);

            if (u_b != 0)
            {
                var ua = ua_t / u_b;
                var ub = ub_t / u_b;

                if (0 <= ua && ua <= 1 && 0 <= ub && ub <= 1)
                {
                    result = new Intersection("Intersection");
                    result.points.Add(
                        new Vector2d(
                            a1.X + ua * (a2.X - a1.X),
                            a1.Y + ua * (a2.Y - a1.Y)
                        )
                    );
                }
                else
                {
                    result = new Intersection("None");
                }
            }
            else if (ua_t == 0 || ub_t == 0)
            {
                result = new Intersection("Coincident");
            }
            else
            {
                result = new Intersection("Parallel");
            }

            return result;
        }

        /**
         *  intersectLinePolygon
         *
         *  @param {module:kld-intersections.Point2D} a1
         *  @param {module:kld-intersections.Point2D} a2
         *  @param {Array<module:kld-intersections.Point2D>} points
         *  @returns {module:kld-intersections.Intersection}
         */
        public static Intersection IntersectLinePolygon(Vector2d a1, Vector2d a2, Vector2d[] points)
        {
            return IntersectLinePolyline(a1, a2, ClosePolygon(points));
        }

        /**
         *  intersectLinePolyline
         *
         *  @param {module:kld-intersections.Point2D} a1
         *  @param {module:kld-intersections.Point2D} a2
         *  @param {Array<module:kld-intersections.Point2D>} points
         *  @returns {module:kld-intersections.Intersection}
         */
        public static Intersection IntersectLinePolyline(Vector2d a1, Vector2d a2, Vector2d[] points)
        {
            var result = new Intersection("None");

            for (var i = 0; i < points.Length - 1; i++)
            {
                var b1 = points[i];
                var b2 = points[i + 1];
                var inter = IntersectLineLine(a1, a2, b1, b2);

                result.appendPoints(inter.points);
            }

            if (result.points.Count > 0)
            {
                result.status = "Intersection";
            }

            return result;
        }

        /**
         *  intersectLineRectangle
         *
         *  @param {module:kld-intersections.Point2D} a1
         *  @param {module:kld-intersections.Point2D} a2
         *  @param {module:kld-intersections.Point2D} r1
         *  @param {module:kld-intersections.Point2D} r2
         *  @returns {module:kld-intersections.Intersection}
         */
        public static Intersection IntersectLineBox(Vector2d a1, Vector2d a2, Box2d box)
        {
            var result = new Intersection("None");
            var lb = box.LeftBottom;
            var lt = box.LeftTop;
            var rb = box.RightBottom;
            var rt = box.RightTop;

            var inter1 = IntersectLineLine(lt, rt, a1, a2);
            var inter2 = IntersectLineLine(rt, rb, a1, a2);
            var inter3 = IntersectLineLine(rb, lb, a1, a2);
            var inter4 = IntersectLineLine(lb, lt, a1, a2);


            result.appendPoints(inter1.points);
            result.appendPoints(inter2.points);
            result.appendPoints(inter3.points);
            result.appendPoints(inter4.points);

            if (result.points.Count > 0)
            {
                result.status = "Intersection";
            }

            return result;
        }
        public static Vector2d IntersectRayLine(LcRay ray, Vector2d segmentStart, Vector2d segmentEnd)
        {
            Vector2d rayOrigin = ray.StartPoint;
            Vector2d rayDirection = ray.Direction;
            float epsilon = 0.0001f; // 用于比较浮点数时的误差范围

            // 计算射线的单位方向向量
            Vector2d rayNormalizedDirection = rayDirection.Normalized;

            // 计算线段的方向向量和垂直向量
            Vector2d segmentDirection = (segmentEnd - segmentStart).Normalized;
            Vector2d segmentNormal = new Vector2d(-segmentDirection.Y, segmentDirection.X);

            if (rayDirection.Equals(segmentDirection) || rayDirection.Equals(-segmentDirection))
            {
                return Vector2d.Zero;
            }

            // 计算射线与线段平面的法向量投影长度
            double normalProjectionLength = -Vector2d.Dot(segmentNormal, (rayOrigin - segmentStart));

            //if (Math.Abs(normalProjectionLength) < epsilon)
            //{
            //    // 射线和线段平行或共面
            //    return Vector2d.Zero;
            //}

            // 计算射线与线段的交点
            double rayDistance = Vector2d.Dot(segmentNormal, rayNormalizedDirection);
            double intersectionDistance = normalProjectionLength / rayDistance;

            if (intersectionDistance < 0)
            {
                // 射线方向与线段相反，无交点
                return Vector2d.Zero;
            }

            Vector2d intersectionPoint = rayOrigin + intersectionDistance * rayNormalizedDirection;

            // 检查交点是否在线段上
            double segmentLength = (segmentEnd - segmentStart).LengthSqrd;
            double distanceToStart = (intersectionPoint - segmentStart).LengthSqrd;
            double distanceToEnd = (intersectionPoint - segmentEnd).LengthSqrd;

            if (distanceToStart > segmentLength || distanceToEnd > segmentLength)
            {
                // 交点不在线段上
                return Vector2d.Zero;
            }

            return intersectionPoint;
        }

        public static Vector2d IntersectXLineLine(LcXLine xline, Vector2d segmentStart, Vector2d segmentEnd)
        {
            Vector2d lineStart = xline.StartPoint;
            Vector2d lineDirection = xline.Direction;

            double x1 = lineStart.X;
            double y1 = lineStart.Y;
            double x2 = lineStart.X + lineDirection.X;
            double y2 = lineStart.Y + lineDirection.Y;

            double x3 = segmentStart.X;
            double y3 = segmentStart.Y;
            double x4 = segmentEnd.X;
            double y4 = segmentEnd.Y;

            // 计算直线的参数
            double a1 = y2 - y1;
            double b1 = x1 - x2;

            // 计算线段的参数
            double a2 = y4 - y3;
            double b2 = x3 - x4;

            // 计算交点坐标
            double determinant = a1 * b2 - a2 * b1;

            if (Math.Abs(determinant) < 0.00001)
            {
                // 直线和线段平行或重合，没有交点
                return Vector2d.Zero;
            }
            else
            {
                // 计算t和u参数
                double c1 = a1 * x1 + b1 * y1;
                double c2 = a2 * x3 + b2 * y3;

                // 计算交点坐标
                double intersectionX = (b2 * c1 - b1 * c2) / determinant;
                double intersectionY = (a1 * c2 - a2 * c1) / determinant;

                // 检查交点是否在给定的线段上
                if (intersectionX >= Math.Min(x3, x4) && intersectionX <= Math.Max(x3, x4) &&
                    intersectionY >= Math.Min(y3, y4) && intersectionY <= Math.Max(y3, y4))
                {
                    return new Vector2d(intersectionX, intersectionY);
                }
                else
                {
                    // 交点不在给定的线段上
                    return Vector2d.Zero;
                }
            }
        }
        public static List<Vector2d> IntersectRayBox(LcRay ray, Box2d box)
        {
            //var result = new Intersection("None");
            var lb = box.LeftBottom;
            var lt = box.LeftTop;
            var rb = box.RightBottom;
            var rt = box.RightTop;

            var interP1 = IntersectRayLine(ray, lt, rt);
            var interP2 = IntersectRayLine(ray, rt, rb);
            var interP3 = IntersectRayLine(ray, rb, lb);
            var interP4 = IntersectRayLine(ray, lb, lt);

            //result.appendPoints(inter1.points);
            //result.appendPoints(inter2.points);
            //result.appendPoints(inter3.points);
            //result.appendPoints(inter4.points);

            //if (result.points.Count > 0)
            //{
            //    result.status = "Intersection";
            //}

            var result = new List<Vector2d>();
            if (!interP1.Equals(Vector2d.Zero))
                result.Add(interP1);

            if (!interP2.Equals(Vector2d.Zero))
                result.Add(interP2);

            if (!interP3.Equals(Vector2d.Zero))
                result.Add(interP3);

            if (!interP4.Equals(Vector2d.Zero))
                result.Add(interP4);

            return result;
        }

        public static List<Vector2d> IntersectXLineBox(LcXLine xline, Box2d box)
        {
            //var result = new Intersection("None");
            var lb = box.LeftBottom;
            var lt = box.LeftTop;
            var rb = box.RightBottom;
            var rt = box.RightTop;

            var interP1 = IntersectXLineLine(xline, lt, rt);
            var interP2 = IntersectXLineLine(xline, rt, rb);
            var interP3 = IntersectXLineLine(xline, rb, lb);
            var interP4 = IntersectXLineLine(xline, lb, lt);

            //result.appendPoints(inter1.points);
            //result.appendPoints(inter2.points);
            //result.appendPoints(inter3.points);
            //result.appendPoints(inter4.points);

            //if (result.points.Count > 0)
            //{
            //    result.status = "Intersection";
            //}

            var result = new List<Vector2d>();
            if (!interP1.Equals(Vector2d.Zero))
                result.Add(interP1);

            if (!interP2.Equals(Vector2d.Zero))
                result.Add(interP2);

            if (!interP3.Equals(Vector2d.Zero))
                result.Add(interP3);

            if (!interP4.Equals(Vector2d.Zero))
                result.Add(interP4);

            return result;
        }
    }
}
